;;; skill-mode.el --- Major mode for editing Skill scripts -*- lexical-binding: t; -*-
;;; Commentary:
;; This package provides a major mode for editing Skill scripts used in
;; Cadence EDA tools.
;;; Code:

(defgroup skill-mode nil
  "Major mode for editing Skill scripts."
  :group 'languages)

(defcustom skill-indent-level 2
  "Number of spaces for each indentation step in `skill-mode'."
  :type 'integer
  :group 'skill-mode
  :safe 'integerp)

(defvar skill-mode-syntax-table
  (let ((table (make-syntax-table)))
  ;; Comments: both ; and // style
  (modify-syntax-entry ?\; "< b" table)
  (modify-syntax-entry ?\n "> b" table)
  (modify-syntax-entry ?/ ". 124" table)
  (modify-syntax-entry ?* ". 23" table)

  ;; Strings
  (modify-syntax-entry ?\" "\"" table)
  (modify-syntax-entry ?\\ "\\" table)

  ;; Operators
  (modify-syntax-entry ?~  "." table)
  (modify-syntax-entry ?!  "." table)
  (modify-syntax-entry ?%  "." table)
  (modify-syntax-entry ?^  "." table)
  (modify-syntax-entry ?&  "." table)
  (modify-syntax-entry ?|  "." table)
  (modify-syntax-entry ?+  "." table)
  (modify-syntax-entry ?-  "." table)
  (modify-syntax-entry ?=  "." table)
  (modify-syntax-entry ?<  "." table)
  (modify-syntax-entry ?>  "." table)
  (modify-syntax-entry ?-  "w" table)
  table))

(defvar skill-keywords
  '("defun" "procedure" "lambda" "let" "foreach" "for" "while"
    "if" "cond" "case" "when" "unless" "and" "or" "not" "nil"
    "t" "list" "cons" "car" "cdr" "setq" "setf" "progn" "exit"
    "error" "warn" "load" "require" "import" "export" "inScheme"
    "inSkill"))

(defvar skill-builtins
  '("printf" "fprintf" "sprintf" "scanf" "fscanf" "sscanf"
    "open" "close" "read" "write" "getchar" "putchar"
    "strcat" "strlen" "substring" "strcmp" "strstr"
    "type" "isCallable" "isFloat" "isInteger" "isList"
    "isString" "isSymbol" "isUserData" "isVector"))

(defvar skill-font-lock-keywords
  `(
    ;; Keywords
    (,(regexp-opt skill-keywords 'symbols) . font-lock-keyword-face)

    ;; Built-in functions
    (,(regexp-opt skill-builtins 'symbols) . font-lock-builtin-face)

    ;; Function definitions
    ("\\<\\(\\sw+\\)\\s-*(" 1 font-lock-function-name-face)

    ;; Variables (starting with ?)
    ; ("\\<?\\sw+" . font-lock-variable-name-face)
    ("\\?\\sw+" . font-lock-variable-name-face)

    ;; Constants (all caps)
    ; ("\\<\\[A-Z0-9_]+\\>" . font-lock-constant-face)
    ("\\<[A-Z0-9_]+\\>" . font-lock-constant-face)

    ;; Special values
    ("\\<t\\>" . font-lock-constant-face)
    ("\\<nil\\>" . font-lock-constant-face)

    ;; Strings
    ("\"\\.\\*\\?" . font-lock-string-face)
    )
  "Highlighting expressions for Skill mode.")

(defun skill-indent-line ()
  "Indent current line as Skill code."
  (interactive)
  (let ((indent-col 0)
        (pos (- (point-max) (point)))
        (point (point)))
    (back-to-indentation)
    (if (looking-at (regexp-opt '(")" "]" "}")))
        (progn
          (save-excursion
            (backward-list 1)
            (setq indent-col (current-column)))
          (if (< indent-col 0) (setq indent-col 0)))
      (save-excursion
        (beginning-of-line)
        (condition-case nil
            (progn
              (backward-up-list 1)
              (forward-char 1)
              (when (not (looking-at "\\s-*$"))
                (forward-sexp 1)
                (backward-sexp 1))
              (setq indent-col (current-column)))
          (error (setq indent-col 0)))))
    (if (< indent-col 0) (setq indent-col 0))
    (beginning-of-line)
    (unless (looking-at (regexp-opt '(")" "]" "}")))
      (indent-line-to indent-col))
    (when (> (- (point-max) pos) (point))
      (goto-char (- (point-max) pos)))))

(defun skill-comment-dwim (arg)
  "Comment or uncomment current line or region in Skill mode."
  (interactive "*P")
  (require 'newcomment)
  (let ((comment-start "//") (comment-end ""))
    (comment-dwim arg)))

;;;###autoload
(define-derived-mode skill-mode prog-mode "Skill"
  "Major mode for editing Skill scripts."
  :syntax-table skill-mode-syntax-table
  (setq-local font-lock-defaults '(skill-font-lock-keywords))
  (setq-local indent-line-function 'skill-indent-line)
  (setq-local comment-start "// ")
  (setq-local comment-end "")
  (setq-local comment-start-skip "//+\\s-*")
  (setq-local comment-use-syntax t)
  (setq-local parse-sexp-ignore-comments t)
  (setq-local electric-indent-chars (append "()[]{}" electric-indent-chars))
  (setq-local indent-tabs-mode nil)
  (setq-local tab-width skill-indent-level)

  ;; Key bindings
  ; (define-key skill-mode-map (kbd "C-c C-c") 'comment-region)
  ; (define-key skill-mode-map (kbd "C-c C-u") 'uncomment-region)
  ; (define-key skill-mode-map (kbd "M-;") 'skill-comment-dwim)
  ; (define-key skill-mode-map (kbd "C-M-a") 'beginning-of-defun)
  ; (define-key skill-mode-map (kbd "C-M-e") 'end-of-defun)
  ; (define-key skill-mode-map (kbd "C-M-h") 'mark-defun)

  ;; Add hook for auto-closing brackets
  (add-hook 'post-self-insert-hook #'electric-pair-post-self-insert-function nil t)

  (modify-syntax-entry ?{ "(}" skill-mode-syntax-table)
  (modify-syntax-entry ?} "){" skill-mode-syntax-table)
  (modify-syntax-entry ?[ "(]" skill-mode-syntax-table)
  (modify-syntax-entry ?] ")[" skill-mode-syntax-table))

;;;###autoload
(add-to-list 'auto-mode-alist '("\\.il\\'" . skill-mode))
(add-to-list 'auto-mode-alist '("\\.ils\\'" . skill-mode))
(add-to-list 'auto-mode-alist '("\\.skill\\'" . skill-mode))

(provide 'skill-mode)
;;; skill-mode.el ends here
